import os
import json
import logging
from xml.etree import ElementTree

# tool_chain action type
PREPROCESS = 0x0001
COMPILE = 0x0002
LINK = 0x0003
COMPILE_LINK = 0x0004
ARCHIVE = 0x0005
RANLIB = 0x0006
STRIP = 0x0007
OBJCOPY = 0x0008

config_json_path = os.path.join(os.path.split(os.path.realpath(__file__))[0], 'compiler_config.json')
compiler_config_key = 'compiler_config'
compiler_config_file = 'compiler_toolchain.xml'


def get_entries(root, xpath):
    names = root.findall(xpath)
    result = []
    for name in names:
        result.append(name.text)
    return result


class CompilerConfig(object):
    archive_names = set()
    gcc_names = set()
    clang_names = set()
    ranlib_names = set()
    ld_names = set()
    strip_names = set()
    ccache_names = set()
    objcopy_names = set()

    @staticmethod
    def read_config(config_file):
        if not os.path.exists(config_file):
            return False

        try:
            root = ElementTree.parse(config_file).getroot()
            gcc_names = get_entries(root, "build/gcc_name")
            clang_names = get_entries(root, "build/clang_name")
            ld_names = get_entries(root, "build/ld")
            ar_names = get_entries(root, "build/archiver")
            ranlib_names = get_entries(root, "build/ranlib")
            strip_names = get_entries(root, "build/strip")
            ccache_names = get_entries(root, "build/ccache")
            objcopy_names = get_entries(root, "build/objcopy")
        except Exception as e:
            logging.exception("Can not parse user configuration file, error message: %s", str(e))
            return False
        else:
            CompilerConfig.archive_names.update(ar_names)
            CompilerConfig.gcc_names.update(gcc_names)
            CompilerConfig.clang_names.update(clang_names)
            CompilerConfig.ranlib_names.update(ranlib_names)
            CompilerConfig.ld_names.update(ld_names)
            CompilerConfig.strip_names.update(strip_names)
            CompilerConfig.ccache_names.update(ccache_names)
            CompilerConfig.objcopy_names.update(objcopy_names)

        return True

    @staticmethod
    def init_config():
        config_path = os.path.join(os.path.split(os.path.realpath(__file__))[0], compiler_config_file)

        if config_json_path and os.path.exists(config_json_path):
            # Read for new config file.
            try:
                with open(config_json_path, 'r') as json_f:
                    config_dict = json.load(json_f)
                    installed_config_path = config_dict.get(compiler_config_key)
                    if installed_config_path is None or not os.path.exists(installed_config_path):
                        pass
                    else:
                        config_path = installed_config_path
            except Exception as e:
                err_string = "Installed config file path is invalid."
                logging.exception("%s, %s", err_string, str(e))
                raise Exception(err_string)

        CompilerConfig.read_config(config_file=config_path)

    @staticmethod
    def parse_user_config(config_path=''):
        if not config_path:
            return True

        abspath = os.path.abspath(config_path)
        if not os.path.exists(abspath):
            return False

        return CompilerConfig.read_config(config_file=config_path)

    @staticmethod
    def create_local_compilers_config(tmp_config_file):
        if not tmp_config_file:
            raise Exception("Not init the tmp configuration path.")

        # Remove the tmp compilers config file at first.
        if os.path.exists(tmp_config_file):
            os.remove(tmp_config_file)

        root = ElementTree.Element("config")
        build_element = ElementTree.SubElement(root, "build")

        dict_config = {
            "gcc_name": list(CompilerConfig.gcc_names),
            "clang_name": list(CompilerConfig.clang_names),
            "archiver": list(CompilerConfig.archive_names),
            "ld": list(CompilerConfig.ld_names),
            "ranlib": list(CompilerConfig.ranlib_names),
            "strip": list(CompilerConfig.strip_names),
            "ccache": list(CompilerConfig.ccache_names),
            "objcopy": list(CompilerConfig.objcopy_names)
        }

        for compiler, list_compilers in list(dict_config.items()):
            for node in list_compilers:
                node_element = ElementTree.SubElement(build_element, compiler)
                node_element.text = node

        try:
            rough_string = ElementTree.tostring(root)
            with open(tmp_config_file, 'w') as fs:
                fs.write(rough_string.decode())
        except Exception as e:
            raise Exception("Dump xml occurs error: %s" % str(e))

    @staticmethod
    def read_local_compilers_config(config_path):

        if not config_path \
                or not os.path.exists(config_path):
            logging.warning("Can not find user compilers configuration file.")
            config_path = os.path.join(os.path.split(os.path.realpath(__file__))[0], compiler_config_file)

            if not os.path.exists(config_path):
                logging.error("Can not find default compilers configuration file.")
                return

        CompilerConfig.read_config(config_file=config_path)

    @staticmethod
    def check_is_archive(command=''):
        (_, sim_cmd) = os.path.split(command)
        if sim_cmd in CompilerConfig.archive_names:
            return True
        else:
            return False

    @staticmethod
    def check_is_gcc(command=''):

        (_, sim_cmd) = os.path.split(command)
        if sim_cmd in CompilerConfig.gcc_names:
            return True
        else:
            return False

    @staticmethod
    def check_is_clang(command=''):

        (_, sim_cmd) = os.path.split(command)
        if sim_cmd in CompilerConfig.clang_names:
            return True
        else:
            return False

    @staticmethod
    def check_is_ranlib(command=''):
 
        (_, sim_cmd) = os.path.split(command)
        if sim_cmd in CompilerConfig.ranlib_names:
            return True
        else:
            return False

    @staticmethod
    def check_is_ld(command=''):

        (_, sim_cmd) = os.path.split(command)
        if sim_cmd in CompilerConfig.ld_names:
            return True
        else:
            return False

    @staticmethod
    def check_is_strip(command=''):
        (_, sim_cmd) = os.path.split(command)
        if sim_cmd in CompilerConfig.strip_names:
            return True
        else:
            return False

    @staticmethod
    def check_is_objcopy(command=''):
        (_, sim_cmd) = os.path.split(command)
        if sim_cmd in CompilerConfig.objcopy_names:
            return True
        else:
            return False